#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <emacs-module.h>

#include "module_c.h"

// !!!!!!!!!
// escape function pointer offset faraway when call from emacs elisp

static struct emacs_runtime *ert_ = NULL;
static emacs_env *env_ = NULL;

emacs_env *(*em_get_environment_forgox) (struct emacs_runtime *ert) = NULL;

emacs_value (*em_make_global_ref_forgox) (emacs_env *env,
                                      emacs_value any_reference) = NULL;

void (*em_free_global_ref_forgox) (emacs_env *env,
                               emacs_value global_reference) = NULL;

enum emacs_funcall_exit (*em_non_local_exit_check_forgox) (emacs_env *env) = NULL;

void (*em_non_local_exit_clear_forgox) (emacs_env *env) = NULL;

enum emacs_funcall_exit (*em_non_local_exit_get_forgox)
    (emacs_env *env,
     emacs_value *non_local_exit_symbol_out,
     emacs_value *non_local_exit_data_out) = NULL;

void (*em_non_local_exit_signal_forgox) (emacs_env *env,
                                     emacs_value non_local_exit_symbol,
                                     emacs_value non_local_exit_data) = NULL;

void (*em_non_local_exit_throw_forgox) (emacs_env *env,
                                    emacs_value tag,
                                    emacs_value value) = NULL;

#include "cgo_define.h"
/*
extern emacs_value em_function_callback(emacs_env *env, ptrdiff_t nargs,
                                        GoSlice args, void *data);
emacs_value em_function_callback_c(emacs_env *env, ptrdiff_t nargs,
                                   emacs_value args[], void *data);
*/
emacs_value (*em_make_function_forgox) (emacs_env *env,
                                        ptrdiff_t min_arity,
                                        ptrdiff_t max_arity,
                                        emacs_value (*function) (emacs_env *env,
                                                                 ptrdiff_t nargs,
                                                                 emacs_value args[],
                                                                 void *)
                                        EMACS_NOEXCEPT,
                                        const char *documentation,
                                        void *data) = NULL;

emacs_value (*em_funcall_forgox) (emacs_env *env,
                                  emacs_value function,
                                  ptrdiff_t nargs,
                                  emacs_value args[]) = NULL;

emacs_value (*em_intern_forgox) (emacs_env *env,
                                 const char *symbol_name) = NULL;

emacs_value (*em_type_of_forgox) (emacs_env *env,
                                  emacs_value value) = NULL;

bool (*em_is_not_nil_forgox) (emacs_env *env, emacs_value value) = NULL;

emacs_value (*em_make_float_forgox) (emacs_env *env, double value) = NULL;

emacs_value (*em_make_string_forgox) (emacs_env *env,
                                  const char *contents, ptrdiff_t length) = NULL;

bool (*em_eq_forgox) (emacs_env *env, emacs_value a, emacs_value b) = NULL;

intmax_t (*em_extract_integer_forgox) (emacs_env *env, emacs_value value) = NULL;

double (*em_extract_float_forgox) (emacs_env *env, emacs_value value) = NULL;

bool (*em_copy_string_contents_forgox) (emacs_env *env,
                                        emacs_value value,
                                        char *buffer,
                                        ptrdiff_t *size_inout) = NULL;

emacs_value (*em_vec_get_forgox) (emacs_env *env, emacs_value vec, ptrdiff_t i) = NULL;

void (*em_vec_set_forgox) (emacs_env *env, emacs_value vec, ptrdiff_t i,
                           emacs_value val) = NULL;

ptrdiff_t (*em_vec_size_forgox) (emacs_env *env, emacs_value vec) = NULL;

emacs_value (*em_make_integer_forgox) (emacs_env *env, intmax_t value) = NULL;


//
void set_module_func_pointer(struct emacs_runtime *ert) {
    if (ert_ != NULL) {
        // already set
        return;
    }

    emacs_env *env = ert->get_environment(ert);
    ert_ = ert;
    env_ = env;

    //
    em_get_environment_forgox = ert_->get_environment;
    em_make_global_ref_forgox = env_->make_global_ref;
    em_free_global_ref_forgox = env_->free_global_ref;
    em_non_local_exit_check_forgox = env_->non_local_exit_check;

    em_non_local_exit_clear_forgox = env_->non_local_exit_clear;

    em_non_local_exit_get_forgox = env_->non_local_exit_get;

    em_non_local_exit_signal_forgox = env_->non_local_exit_signal;

    em_non_local_exit_throw_forgox = env_->non_local_exit_throw;

/*
extern emacs_value em_function_callback(emacs_env *env, ptrdiff_t nargs,
                                        GoSlice args, void *data);
emacs_value em_function_callback_c(emacs_env *env, ptrdiff_t nargs,
                                   emacs_value args[], void *data);
*/
    em_make_function_forgox = env_->make_function;

    em_funcall_forgox = env_->funcall;

    em_intern_forgox = env_->intern;

    em_type_of_forgox = env_->type_of;

    em_is_not_nil_forgox = env_->is_not_nil;

    em_make_float_forgox = env_->make_float;

    em_make_string_forgox = env_->make_string;

    em_eq_forgox = env_->eq;

    em_extract_integer_forgox = env_->extract_integer;

    em_extract_float_forgox = env_->extract_float;

    em_copy_string_contents_forgox = env_->copy_string_contents;

    em_vec_get_forgox = env_->vec_get;

    em_vec_set_forgox = env_->vec_set;

    em_vec_size_forgox = env_->vec_size;

    em_make_integer_forgox = env_->make_integer;

}

emacs_env *em_get_environment_forgo (struct emacs_runtime *ert) {
    return em_get_environment_forgox(ert);
    // return ert->get_environment(ert);
}

emacs_value em_make_global_ref_forgo (emacs_env *env,
				  emacs_value any_reference) {
    return em_make_global_ref_forgox(env, any_reference);
    // return env->make_global_ref(env, any_reference);
}

void em_free_global_ref_forgo (emacs_env *env,
			   emacs_value global_reference) {
    return em_free_global_ref_forgox(env, global_reference);
    // return env->free_global_ref(env, global_reference);
}

enum emacs_funcall_exit em_non_local_exit_check_forgo (emacs_env *env) {
    return em_non_local_exit_check_forgox(env);
    // return env->non_local_exit_check(env);
}

void em_non_local_exit_clear_forgo (emacs_env *env) {
    return em_non_local_exit_clear_forgox(env);
    // return env->non_local_exit_clear(env);
}

enum emacs_funcall_exit em_non_local_exit_get_forgo
    (emacs_env *env,
     emacs_value *non_local_exit_symbol_out,
     emacs_value *non_local_exit_data_out) {
    return em_non_local_exit_get_forgox(env, non_local_exit_symbol_out, non_local_exit_data_out);
    // return env->non_local_exit_get(env, non_local_exit_symbol_out, non_local_exit_data_out);
}

void em_non_local_exit_signal_forgo (emacs_env *env,
				 emacs_value non_local_exit_symbol,
				 emacs_value non_local_exit_data) {
    return em_non_local_exit_signal_forgox(env, non_local_exit_symbol, non_local_exit_data);
    // return env->non_local_exit_signal(env, non_local_exit_symbol, non_local_exit_data);
}

void em_non_local_exit_throw_forgo (emacs_env *env,
				emacs_value tag,
				emacs_value value) {
    return em_non_local_exit_throw_forgox(env, tag, value);
    // return env->non_local_exit_throw(env, tag, value);
}

#include "cgo_define.h"
extern emacs_value em_function_callback(emacs_env *env, ptrdiff_t nargs,
                                        GoSlice args, void *data);
emacs_value em_function_callback_c(emacs_env *env, ptrdiff_t nargs,
                                        emacs_value args[], void *data) {
    GoSlice args_go;
    args_go.data = args;
    args_go.len = nargs;
    args_go.cap = nargs;
    return em_function_callback(env, nargs, args_go, data);
}
emacs_value em_make_function_forgo (emacs_env *env,
				ptrdiff_t min_arity,
				ptrdiff_t max_arity,
				emacs_value (*function) (emacs_env *env,
							 ptrdiff_t nargs,
							 emacs_value args[],
							 void *)
				  EMACS_NOEXCEPT,
				const char *documentation,
				void *data) {
    function = em_function_callback_c;
    return em_make_function_forgox(env, min_arity, max_arity, function, documentation, data);
    // return env->make_function(env, min_arity, max_arity, function, documentation, data);
}

emacs_value em_funcall_forgo (emacs_env *env,
                          emacs_value function,
                          ptrdiff_t nargs,
                          emacs_value args[]) {
    return em_funcall_forgox(env, function, nargs, args);
    // return env->funcall(env, function, nargs, args);
}

emacs_value em_intern_forgo (emacs_env *env,
                         const char *symbol_name) {
    // fprintf(stderr, "=== %p, %d==?%d, %s\n", env, strlen("fset"), strlen(symbol_name), symbol_name);
    // emacs_value v = env->intern(env, "provide");
    emacs_value v = em_intern_forgox(env, symbol_name);
    // emacs_value v = env->intern(env, symbol_name);
    // fprintf(stderr, "=== %p, %d==?%d, %s, %p\n", env, strlen("fset"), strlen(symbol_name), symbol_name, v);
    return v;
}

emacs_value em_type_of_forgo (emacs_env *env,
			  emacs_value value) {
    return em_type_of_forgox(env, value);
    // return env->type_of(env, value);
}

bool em_is_not_nil_forgo (emacs_env *env, emacs_value value) {
    return em_is_not_nil_forgox(env, value);
    // return env->is_not_nil(env, value);
}

emacs_value em_make_float_forgo (emacs_env *env, double value) {
    return em_make_float_forgox(env, value);
    // return env->make_float(env, value);
}

emacs_value em_make_string_forgo (emacs_env *env,
	const char *contents, ptrdiff_t length) {
    return em_make_string_forgox(env, contents, length);
    // return env->make_string(env, contents, length);
}

bool em_eq_forgo (emacs_env *env, emacs_value a, emacs_value b) {
    return em_eq_forgox(env, a, b);
    // return env->eq(env, a, b);
}

intmax_t em_extract_integer_forgo (emacs_env *env, emacs_value value) {
    return em_extract_integer_forgox(env, value);
    // return env->extract_integer(env, value);
}

double em_extract_float_forgo (emacs_env *env, emacs_value value) {
    return em_extract_float_forgox(env, value);
    // return env->extract_float(env, value);
}

bool em_copy_string_contents_forgo (emacs_env *env,
                                emacs_value value,
                                char *buffer,
                                ptrdiff_t *size_inout) {
    return em_copy_string_contents_forgox(env, value, buffer, size_inout);
    // return env->copy_string_contents(env, value, buffer, size_inout);
}

emacs_value em_vec_get_forgo (emacs_env *env, emacs_value vec, ptrdiff_t i) {
    return em_vec_get_forgox(env, vec, i);
    // return env->vec_get(env, vec, i);
}

void em_vec_set_forgo (emacs_env *env, emacs_value vec, ptrdiff_t i,
		   emacs_value val) {
    return em_vec_set_forgox(env, vec, i, val);
    // return env->vec_set(env, vec, i, val);
}

ptrdiff_t em_vec_size_forgo (emacs_env *env, emacs_value vec) {
    return em_vec_size_forgox(env, vec);
    // return env->vec_size(env, vec);
}

emacs_value em_make_integer_forgo (emacs_env *env, intmax_t value) {
    // fprintf(stderr, "wtf111: %p, %d, %p\n", env, value, em_make_integer_forgox);
    // fprintf(stderr, "wtf222: %p, %d\n", env->make_integer, value);
    // emacs_value rv = env->make_integer(env, value);
    emacs_value rv = em_make_integer_forgox(env, value);
    return rv;
}

